package com.yhb.channel.utils.encryptAndSign;

import com.yhb.channel.impl.YPL.YPLConstant;
import com.yhb.channel.impl.YPL.YPLException;
import lombok.Data;
import org.jetbrains.annotations.NotNull;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.*;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

@Data
public class YPLRSAUtils {

    public static final String KEY_ALGORITHM = "RSA";

    private String charset; // 默认 UTF-8
    private String algorithm; // 默认 SHA256withRSA
    private static final int MAX_ENCRYPT_BLOCK = 245; // RSA最大加密明文大小
    private static final int MAX_DECRYPT_BLOCK = 203; // RSA最大解密密文大小
    public static final Base64.Encoder base64Encoder = Base64.getEncoder();
    public static final Base64.Decoder base64Decoder = Base64.getDecoder();

    public YPLRSAUtils() {
        this.charset = "UTF-8";
        this.algorithm = "SHA256withRSA";
    }

    public YPLRSAUtils(String charset, String algorithm) {
        this.charset = charset;
        this.algorithm = algorithm;
    }

    /**
     * 获取公钥字符串 只能用户获取公钥 私钥请使用ChannelKeyUtils(因为要密码)
     *
     * @param keyPath 路径
     *
     * @return 密钥
     */
    @NotNull
    public static String getKeyFromKeyPath(String keyPath) {
        File file = new File(keyPath);
        if (!file.exists()) {
            throw new YPLException(YPLConstant.ENCRYPT_OR_SIGN_ERROR, "证书文件不存在:" + keyPath);
        }
        BufferedReader reader = null;
        StringBuffer keyString = new StringBuffer();
        String tempString = null;
        try {
            reader = new BufferedReader(new FileReader(file));
            while ((tempString = reader.readLine()) != null) {
                if (tempString.startsWith("--")) {
                    continue;
                }
                keyString.append(tempString);
            }
            reader.close();
        } catch (IOException e) {
            throw new YPLException(YPLConstant.ENCRYPT_OR_SIGN_ERROR, "证书文件读取异常:" + keyPath);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return keyString.toString();
    }

    private PrivateKey convertStrToPrivateKey(@NotNull String keyStr) {
        try {
            // PKCS8补全Key
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(
                    base64Decoder.decode(keyStr.getBytes(this.charset)));
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return keyFactory.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException | UnsupportedEncodingException e) {
            throw new YPLException(YPLConstant.ENCRYPT_OR_SIGN_ERROR, "转化私钥失败", e);
        }
    }

    private PublicKey convertStrToPublicKey(@NotNull String keyStr) {
        try {
            // X509证书
            byte[] publicKeyBytes = base64Decoder.decode(keyStr.getBytes(this.charset));
//            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
//            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
//            return keyFactory.generatePublic(keySpec);
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            InputStream in = new ByteArrayInputStream(publicKeyBytes);
            X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate(in);
            return certificate.getPublicKey();
        } catch (UnsupportedEncodingException | CertificateException e) {
            throw new YPLException(YPLConstant.ENCRYPT_OR_SIGN_ERROR, "转化公钥失败", e);
        }
    }

    /**
     * 签名
     *
     * @param privateKey 私钥字符串 由KeyUtils得来（文件路径->KeyUtils->String)
     * @param content    签名原文
     *
     * @return 签名RSA签名密文
     */
    public String sign(String privateKey, String content) {
        try {
            Signature signature = Signature.getInstance(this.algorithm);
            signature.initSign(this.convertStrToPrivateKey(privateKey));
            signature.update(content.getBytes(this.charset));
            return base64Encoder.encodeToString(signature.sign());
        } catch (Exception e) {
            throw new YPLException(YPLConstant.ENCRYPT_OR_SIGN_ERROR, "签名失败", e);
        }
    }

    /**
     * 验签
     *
     * @param publicKey 公钥字符串 this.getKeyFromKeyPath得到
     * @param content   验签原文
     * @param signStr   签名字符串
     *
     * @return 验签结果
     */
    public boolean verify(String publicKey, @NotNull String content, @NotNull String signStr) {
        try {
            Signature signature = Signature.getInstance(this.algorithm);
            signature.initVerify(convertStrToPublicKey(publicKey));
            signature.update(content.getBytes(this.charset));
            return signature.verify(base64Decoder.decode(signStr.getBytes(this.charset)));
        } catch (
                NoSuchAlgorithmException | InvalidKeyException | UnsupportedEncodingException | SignatureException e
        ) {
            throw new YPLException(YPLConstant.ENCRYPT_OR_SIGN_ERROR, "验签失败", e);
        }
    }

    /**
     * 公钥加密
     */
    public byte[] encryptByPublicKey(byte[] content, String publicKey, int segmentSize) {
        try {
            PublicKey pk = this.convertStrToPublicKey(publicKey);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); // java默认 "RSA"同"RSA/ECB/PKCS1Padding"
            cipher.init(Cipher.ENCRYPT_MODE, pk);
            return this.segmentDoFinal(content, cipher, segmentSize);
        } catch (
                IOException | NoSuchAlgorithmException | InvalidKeyException
                        | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException e
        ) {
           throw new YPLException(YPLConstant.ENCRYPT_OR_SIGN_ERROR, "加密失败", e);
        }
    }
    public String encryptByPublicKey(@NotNull String data, String publicKey) {
        try {
            byte[] resData = encryptByPublicKey(data.getBytes(this.charset), publicKey, MAX_ENCRYPT_BLOCK);
            return base64Encoder.encodeToString(resData);
        } catch (UnsupportedEncodingException e) {
            throw new YPLException(YPLConstant.ENCRYPT_OR_SIGN_ERROR, "加密失败", e);
        }
    }

    /**
     * 私钥解密
     */
    public byte[] decryptByPrivateKey(byte[] content, String privateKey, int segmentSize) {
        try {
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, this.convertStrToPrivateKey(privateKey));
            return segmentDoFinal(content, cipher, segmentSize);
        } catch (
                IOException | NoSuchAlgorithmException | InvalidKeyException
                        | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException e
        ) {
            throw new YPLException(YPLConstant.ENCRYPT_OR_SIGN_ERROR, "解密失败", e);
        }
    }
    public String decryptByPrivateKey(@NotNull String data, String privateKey) {
        try {
            byte[] resData = decryptByPrivateKey(data.getBytes(this.charset), privateKey, MAX_DECRYPT_BLOCK);
            return base64Encoder.encodeToString(resData);
        } catch (UnsupportedEncodingException e) {
            throw new YPLException(YPLConstant.ENCRYPT_OR_SIGN_ERROR, "解密失败", e);
        }
    }


    /**
     * 分段加解密
     *
     * @param content     内容
     * @param cipher      加解密器
     * @param segmentSize 分段长度
     */
    @NotNull
    private byte[] segmentDoFinal(@NotNull byte[] content, Cipher cipher, int segmentSize)
            throws BadPaddingException, IllegalBlockSizeException, IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int inputLen = content.length;
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段加/解密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > segmentSize) {
                cache = cipher.doFinal(content, offSet, segmentSize);
            } else {
                cache = cipher.doFinal(content, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * segmentSize;
        }
        byte[] data = out.toByteArray();
        out.close();
        return data;
    }

}
